package fybug.nulll.pdstream.IO.File;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;

import org.jetbrains.annotations.NonNls;

import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;

import fybug.nulll.pdfunctionlibrary.Processing.Err.Stop;
import fybug.nulll.pdfunctionlibrary.Util.Processing.Files;
import fybug.nulll.pdstream.IO.Error.Path.ParamNoDir;
import fybug.nulll.pdstream.IO.NI_O;

import static fybug.nulll.pdfunctionlibrary.Util.Processing.Arrarys.conversionArrayType;
/**
 * <h2>目录操作器.</h2>
 * <pre>
 * 提供清空、删除文件夹
 * 清除空文件夹
 * 批量移动、复制文件夹等操作
 * 可以扫描空文件夹和空文件
 *
 * <b>除去批量操作方法，单独的操作方法通常为静态方法</b>
 * </pre>
 *
 * @author fybug
 * @version 0.0.4
 * @see AbstractFileOper
 * @see ParamNoDir
 * @since JDK 1.8
 */
@SuppressWarnings( "all" )
public final
class DirOper extends AbstractFileOper {
    /** <p>构造一个目录操作器并绑定路径组.</p> */
    public
    DirOper(@Nullable final File... files) {super(files);}

    /** <p>构造一个目录操作器并绑定路径组.</p> */
    public
    DirOper(@Nullable final String... s) {super(s);}

    /*
     * File
     */

    /**
     * <p>重绑定目录组.</p>
     * <p>会过滤出路径组中的文件夹</p>
     */
    @NotNull
    @Override
    public
    DirOper binFile(@Nullable final File... files) {
        // 过滤出文件
        return (DirOper) super.binFile(conversionArrayType(files, (file) -> {
            if (file.isFile() || !file.isDirectory())
                return null;
            return file;
        }, File.class));
    }

    /**
     * <p>增加绑定的目录组</p>
     *
     * @see #binFile(File...)
     */
    @Override
    @NotNull
    public
    DirOper appendFile(@Nullable final File... files) {
        // 过滤出文件
        return (DirOper) super.appendFile(conversionArrayType(files, (file) -> {
            if (file.isFile() || !file.isDirectory())
                return null;
            return file;
        }, File.class));
    }

    /*
     * Oper
     */

    /**
     * <p>将绑定的 <b>目录</b> 移动 <b>该文件夹下</b>.</p>
     * <pre>
     * 将会保持文件的命名
     * <b>并更新内部路径组为移动后的路径</b>
     *
     * 如绑定的源文件夹为：{@code File("/data/A"),File("/data/S");}
     * 移动到{@code File("/data/B");}
     * 则移动后的路径为：{@code File("/data/B/A"),File("/data/B/S");}
     * </pre>
     *
     * @param to 复制到该文件夹下
     */
    @Override
    @NotNull
    public
    DirOper mv(@NotNull final File to) throws IOException {
        // 检查参数
        noUserFile(to);
        @NotNull File[] from;
        try {
            from = file();
        } catch ( Stop e ) {
            return this;
        }
        @NotNull File file;
        synchronized ( from ){
            for ( int i = 0; i < from.length; i++ ){
                file = from[i];
                mv(file, (from[i] = new File(to, file.getName())));
            }
        }
        return this;
    }

    /**
     * <p>移动文件夹.</p>
     * <pre>
     * 将 <b>文件夹</b> 移动为另一个路径
     * 会将文件夹 <b>重命名</b>
     * </pre>
     *
     * @param from 被移动的文件夹
     * @param to   移动为该路径
     *
     * @return 移动后的路径
     */
    public static
    File mv(@NotNull final File from, @NotNull final File to) throws IOException {
        checkDir(from);
        noUserFile(to);
        int fromSize = from.toString().length(); // 文件路径长度
        to.mkdirs();
        @NotNull //* 数组队列，作为栈堆比 Stack 快 *//
                Deque<File> dir; // 要移动的的文件夹
        // 获取初始文件夹内容
        dir = new ArrayDeque<>(getFolderS(from));
        if (dir.size() == 0)
            /* 无可扫描 */
            return to;
        @Nullable File[] files; // 当前文件夹下的子路径
        @Nullable File file; // 当前目录
        @NotNull String name;
        try {
            while( true ){
                /* 清空所有文件夹以及子路径 */
                // 弹出当前目录
                file = dir.element();
                name = file.toString(); // 文件名
                // 裁剪文件名
                name = Files.fileToString(new File(name.substring(fromSize)));
                if (file.isFile()) {
                    // 移动文件
                    FilerOper.mv(file, new File(to, name));
                    continue;
                } else {
                    // 获取当前路径下的内容
                    files = file.listFiles();
                    if (files == null)
                        files = new File[0];
                    new File(to, name).mkdirs();
                }
                if (files.length == 0)
                    /* 该文件夹清除完成 */
                    dir.remove().delete();
                else
                    // 放入下次处理
                    dir.push(files[0]);
            }
        } catch ( NoSuchElementException ignored ) {
            name = null;
            files = null;
            fromSize = 0;
            dir = null;
            file = null;
        }
        // 删除源文件
        from.delete();
        return to;
    }

    /**
     * <p>复制绑定的目录组到指定 <b>文件夹中</b> .</p>
     * <pre>
     * 将该组目录复制到 <b>指定的目录中</b>
     *
     * 如绑定的源文件夹为：{@code File("/data/A"),File("/data/S");}
     * 复制到{@code File("/data/B");}
     * 则复制后的路径为：{@code File("/data/B/A"),File("/data/B/S");}
     * </pre>
     *
     * @param to 复制到该路径
     */
    @NotNull
    public
    DirOper copy(@NotNull final File to) {
        noUserFile(to);
        @NotNull File from[];
        try {
            from = file();
        } catch ( Stop e ) {
            return this;
        }
        to.mkdirs();
        @NotNull File file;
        synchronized ( from ){
            for ( int i = 0; i < from.length; i++ ){
                file = from[i];
                copy(file, new File(to, file.getName()));
            }
        }
        return this;
    }

    /**
     * <p>复制为另一个目录.</p>
     * <pre>
     * 将 <b>该目录</b> 复制到 <b>另一个目录</b> 并重命名
     * </pre>
     *
     * @param from 要复制的文件夹
     * @param to   复制为该路径
     *
     * @return 复制后的路径
     */
    public static
    File copy(@NotNull final File from, @NotNull final File to) {
        checkDir(from);
        noUserFile(to);
        final int nameSize = from.toString().length(); // 初始文件名长度
        @NotNull //* 使用 ArrayDeque 作为栈堆 *//
                Deque<Deque<File>> dir = new ArrayDeque<>(); // 目录栈
        @NotNull Deque<File> nowBody; // 当前目录内容栈堆
        // 获取初始文件夹内容
        dir.add(getFolderS(from));
        to.mkdirs();
        if (dir.size() == 0)
            /* 无可处理 */
            return to;
        @NotNull File file; // 当前内容
        @NotNull @NonNls String name;
        try {
            while( true ){
                /* 处理所有目录 */
                // 取出当前目录
                nowBody = dir.element();
                try {
                    while( true ){
                        /* 处理当前目录 */
                        file = nowBody.remove();
                        // 裁剪文件名
                        name = file.toString().substring(nameSize);
                        // 使用零复制
                        if (file.isFile())
                            try {
                                NI_O.zerCopy(file, new File(to, name));
                            } catch ( IOException ignored ) {
                            }
                        else if (file.isDirectory()) {
                            // 建立文件夹
                            new File(to, name).mkdirs();
                            // 加入目录栈
                            dir.add(getFolderS(file));
                            break;
                        }
                    }
                } catch ( NoSuchElementException e ) {
                    name = null;
                    file = null;
                    nowBody = null;
                    // 弹出当前目录
                    dir.remove();
                }
            }
        } catch ( NoSuchElementException ignored ) {
        }
        return to;
    }

    /** <p>将 <b>绑定的文件夹</b> 中的 <b>空文件夹</b> 清除，并更新内部路径组.</p> */
    @NotNull
    public
    DirOper cleanEmpty() {
        @NotNull File[] files;
        try {
            files = file();
        } catch ( Stop e ) {
            return this;
        }
        @NotNull File file, tmp[];
        synchronized ( files ){
            for ( int i = 0; i < files.length; i++ ){
                file = files[i];
                tmp = file.listFiles();
                if (tmp == null || tmp.length == 0) {
                    file.delete();
                    files[i] = null;
                }
            }
            tmp = null;
            file = null;
            binFile(files);
        }
        return this;
    }

    /**
     * <p>清除空文件夹.</p>
     * <pre>
     * <b>扫描</b> 该文件夹下的 <b>空文件夹</b>接着将其删除
     * </pre>
     *
     * @param f 要清除空文件夹的文件夹
     *
     * @return 该文件夹
     */
    public static
    File cleanEmpty(@NotNull final File f) {
        @NotNull File[] files = emptyFolder(f); // 空文件夹
        for ( int i = 0; i < files.length; i++ )
            // 删除
            files[i].delete();
        return f;
    }

    /**
     * <p>清空绑定的文件夹.</p>
     * <pre>
     * 清除 <b>绑定的文件夹</b> 中的内容
     * 不会清除绑定的文件夹
     * </pre>
     *
     * @see #cleanDir(File)
     */
    @NotNull
    public
    DirOper cleanDir() {
        @NotNull File[] files;
        try {
            files = file();
        } catch ( Stop e ) {
            return this;
        }
        for ( int i = 0; i < files.length; i++ )
            cleanDir(files[i]);
        return this;
    }

    /**
     * <p>清空文件夹.</p>
     * <pre>
     * 使用 <b>递归删除</b> 删除文件夹下的 <b>所有内容</b>
     * 删除后源文件夹 <b>仍会存在</b>
     * 如果该文件夹 <b>不存在</b> 则会生成一个 <b>空文件夹</b>
     * </pre>
     *
     * @param f 要清除的文件夹
     *
     * @return 该文件夹
     */
    public static
    File cleanDir(@NotNull final File f) {
        if (noUserFile(f)) {
            f.mkdirs();
            // 无需处理
            return f;
        }
        @NotNull //* 数组队列，作为栈堆比 Stack 快 *//
        final Deque<File> dir = new ArrayDeque<>(); // 要清除的文件夹
        @Nullable File[] listFiles = f.listFiles(); // 获取初始文件夹内容
        if (listFiles == null || listFiles.length == 0)
            return f;
        for ( int i = 0; i < listFiles.length; i++ )
            /* 放入所有内容 */
            if (listFiles[i] != null)
                dir.push(listFiles[i]);
        @Nullable File[] files; // 当前文件夹下的子路径
        @Nullable File file; // 当前目录
        try {
            while( true ){
                /* 清空所有文件夹以及子路径 */
                // 弹出当前目录
                file = dir.element();
                if (file.isFile()) {
                    file.delete();
                    continue;
                } else {
                    // 获取当前路径下的内容
                    files = file.listFiles();
                    if (files == null)
                        /* 空数据 */
                        files = new File[0];
                }
                // 删除该文件夹
                if (files.length == 0)
                    dir.remove().delete();
                else
                    // 放入下次处理
                    dir.push(files[0]);
            }
        } catch ( NoSuchElementException ignored ) {
        }
        return f;
    }

    /**
     * <p>删除绑定的文件夹,并清除绑定.</p>
     * <pre>
     * 清除绑定的文件夹的内容并删除源文件夹
     * 会清空所有的绑定
     * </pre>
     *
     * @see #deleDir(File)
     */
    @NotNull
    public
    DirOper deleDir() {
        @NotNull File[] files;
        try {
            files = file();
        } catch ( Stop e ) {
            return this;
        }
        for ( int i = 0; i < files.length; i++ )
            deleDir(files[i]);
        binFile();
        return this;
    }

    /**
     * <p>删除文件夹.</p>
     * <pre>
     * 使用 {@link #cleanDir(File)} 清除文件夹下所有的内容
     * 并删除文件夹自身
     * </pre>
     *
     * @param f 要删除的文件夹
     */
    public static
    void deleDir(@NotNull final File f) {
        // 虚路径无需清理
        if (noUserFile(f))
            return;
        // 清空文件夹
        cleanDir(f);
        // 删除自身
        f.delete();
    }

    /**
     * <p>扫描空文件夹.</p>
     *
     * @param f 要扫描的文件夹
     *
     * @return 返回扫描出的空文件夹路径列表
     */
    @NotNull
    public static
    File[] emptyFolder(@NotNull final File f) {
        checkDir(f);
        @NotNull //* Stack<File>, int, File *//
                Deque<Object[]> catalog = new ArrayDeque<>(); // 目录层次
        @NotNull //* 在经常修改的情况下用 LinkedList 更快 *//
                LinkedList<File> empty = new LinkedList<>(); // 扫描出的数据
        // 放入初始扫描内容
        catalog.push(getFolder(f));
        @NotNull Deque<File> dir; // 当前层的文件夹
        @NotNull Object[] objects;
        @Nullable File nowFile;
        try {
            while( true ){
                /* 扫描所有文件夹 */
                objects = catalog.remove(); // 取出当前数据
                dir = (Deque<File>) objects[0];
                // 如果已是是空文件夹
                if ((int) objects[1] == 0) {
                    // 添加到结果列表
                    empty.add((File) objects[2]);
                    // 获取上一层内容
                    objects = catalog.element();
                    // 修改上一层的内容数
                    objects[1] = ((int) objects[1]) - 1;
                    continue;
                }
                if (dir.size() == 0)
                    continue;
                // 仍有内容,压回栈中
                catalog.push(objects);
                nowFile = dir.pop(); // 已检查堆栈大小,无需担心到达栈底
                if (nowFile == null)
                    continue;
                // 放入压入栈中,准备下次扫描
                catalog.push(getFolder(nowFile));
            }
        } catch ( NoSuchElementException ignored ) {
            dir = null;
            objects = null;
            nowFile = null;
            catalog = null;
        }
        @NotNull File[] out = new File[empty.size()];
        return empty.toArray(out);
    }

    /**
     * <p>扫描空文件.</p>
     *
     * @param f 要扫描的文件夹
     *
     * @return 返回扫描出的空文件路径列表
     */
    @NotNull
    public static
    File[] emptyFile(@NotNull final File f) {
        // 检查参数
        checkDir(f);
        @NotNull Deque<Deque<File>> catalog = new ArrayDeque<>(); // 目录层次
        @NotNull //* 在经常修改的情况下用 LinkedList 更快 *//
                LinkedList<File> linkedList = new LinkedList<>(); // 扫描出的数据
        // 初始化扫描内容
        catalog.push(getFolderS(f));
        @NotNull Deque<File> data; // 当前层次
        @Nullable File file;
        try {
            while( true ){
                /* 扫描所有文件夹 */
                data = catalog.remove(); // 取出当前数据
                try {
                    while( true ){
                        /* 处理当前层次的内容 */
                        // 获取当前层次中的一个路径
                        file = data.remove();
                        if (file == null)
                            continue;
                        if (file.isFile()) {
                            /* 判断空文件 */
                            if (file.length() == 0)
                                linkedList.add(file);
                        } else if (file.isDirectory()) {
                            // 压回栈中
                            catalog.push(data);
                            // 放入下次扫描
                            catalog.push(getFolderS(file));
                            // 进入下一层次
                            break;
                        }
                    }
                } catch ( NoSuchElementException ignored ) {
                    file = null;
                }
            }
        } catch ( NoSuchElementException ignored ) {
            data = null;
            catalog = null;
        }
        return linkedList.toArray(new File[linkedList.size()]);
    }

    /**
     * <p>获取目录下的相关数据.</p>
     *
     * <pre>
     * 会获取到该文件夹下的所有文件夹
     * 并放入栈堆中
     * 并会获取所有的内容数量,以及该目录自身
     *
     * <b>需要提前进行过滤,保证是文件夹且不是空对象</b>
     * </pre>
     *
     * @param f 要获取的目录
     *
     * @return 返回获得的数据
     * {@Hide}
     */
    @NotNull
    private static
    Object[] getFolder(@NotNull final File f) {
        @NotNull //* 数据格式: Deque<File>, int, File *//
                Object[] objects = new Object[]{null, null, null}; // 当前目录层次存放的数据
        @Nullable //* 使用数组保证遍历速度,降低开销 *//
                File[] files = f.listFiles(); // 当前的目录中的所有文件 / 文件夹
        @NotNull //* 使用链表,加快后面的读取速度并减少开销 *//
                Deque<File> dir = new LinkedList<>(); // 当前目录中的所有文件夹
        if (files == null)
            /* 检查空返回 */
            files = new File[0];
        // 获取目录内容总数
        objects[1] = files.length;
        // 当前层次的目录
        objects[2] = f;
        for ( int i = 0; i < files.length; i++ ){
            /* 过滤所有路径 */
            if (files[i] == null)
                /* 检查空内容 */
                continue;
            if (files[i].isDirectory())
                /* 取出所有文件夹 */
                dir.push(files[i]);
        }
        // 放入过滤后的内容
        objects[0] = dir;
        return objects;
    }

    /**
     * <p>获取目录下的相关数据.</p>
     *
     * <pre>
     * 会获取到该文件夹下的所有文件夹
     * 并放入栈堆中
     *
     * <b>需要提前进行过滤,保证是文件夹且不是空对象</b>
     * </pre>
     *
     * @param f 要获取的目录
     *
     * @return 返回获得的数据
     * {@Hide}
     */
    @NotNull
    private static
    Deque<File> getFolderS(@NotNull final File f) {
        @NotNull //* 使用链表,加快后面的读取速度并减少开销 *//
                Deque<File> file = new LinkedList<>(); // 当前目录中的所有内容
        @NotNull final File[] files = f.listFiles(); // 当前目录中的所有内容
        if (files != null && files.length > 0)
            /* 检查是否有内容 */
            for ( int i = 0; i < files.length; i++ ){
                /* 过滤所有路径 */
                if (files[i] != null)
                    /* 检查空内容 */
                    file.push(files[i]);
            }
        return file;
    }
}
